package com.github.faucamp.simplertmp.io;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import com.github.faucamp.simplertmp.Util;
import com.github.faucamp.simplertmp.packets.RtmpHeader;

/**
 * Chunk stream channel information
 *
 * @author francois, leo
 */
public class ChunkStreamInfo {

  public static final byte RTMP_CID_PROTOCOL_CONTROL = 0x02;
  public static final byte RTMP_CID_OVER_CONNECTION = 0x03;
  public static final byte RTMP_CID_OVER_CONNECTION2 = 0x04;
  public static final byte RTMP_CID_OVER_STREAM = 0x05;
  public static final byte RTMP_CID_VIDEO = 0x06;
  public static final byte RTMP_CID_AUDIO = 0x07;
  private RtmpHeader prevHeaderRx;
  private RtmpHeader prevHeaderTx;
  private static long sessionBeginTimestamp;
  private long realLastTimestamp = System.nanoTime() / 1000000;  // Do not use wall time!
  private ByteArrayOutputStream baos = new ByteArrayOutputStream(1024 * 128);

  /** @return the previous header that was received on this channel, or <code>null</code> if no previous header was received */
  public RtmpHeader prevHeaderRx() {
    return prevHeaderRx;
  }

  /** Sets the previous header that was received on this channel, or <code>null</code> if no previous header was sent */
  public void setPrevHeaderRx(RtmpHeader previousHeader) {
    this.prevHeaderRx = previousHeader;
  }

  /** @return the previous header that was transmitted on this channel */
  public RtmpHeader getPrevHeaderTx() {
    return prevHeaderTx;
  }

  public boolean canReusePrevHeaderTx(RtmpHeader.MessageType forMessageType) {
    return (prevHeaderTx != null && prevHeaderTx.getMessageType() == forMessageType);
  }

  /** Sets the previous header that was transmitted on this channel */
  public void setPrevHeaderTx(RtmpHeader prevHeaderTx) {
    this.prevHeaderTx = prevHeaderTx;
  }

  /** Sets the session beginning timestamp for all chunks */
  public static void markSessionTimestampTx() {
    sessionBeginTimestamp = System.nanoTime() / 1000000;
  }

  /** Utility method for calculating & synchronizing transmitted timestamps */
  public long markAbsoluteTimestampTx() {
    return System.nanoTime() / 1000000 - sessionBeginTimestamp;
  }

  /** Utility method for calculating & synchronizing transmitted timestamp deltas */
  public long markDeltaTimestampTx() {
    long currentTimestamp = System.nanoTime() / 1000000;
    long diffTimestamp = currentTimestamp - realLastTimestamp;
    realLastTimestamp = currentTimestamp;
    return diffTimestamp;
  }

  /** @return <code>true</code> if all packet data has been stored, or <code>false</code> if not */
  public boolean storePacketChunk(InputStream in, int chunkSize) throws IOException {
    final int remainingBytes = prevHeaderRx.getPacketLength() - baos.size();
    byte[] chunk = new byte[Math.min(remainingBytes, chunkSize)];
    Util.readBytesUntilFull(in, chunk);
    baos.write(chunk);
    return (baos.size() == prevHeaderRx.getPacketLength());
  }

  public ByteArrayInputStream getStoredPacketInputStream() {
    ByteArrayInputStream bis = new ByteArrayInputStream(baos.toByteArray());
    baos.reset();
    return bis;
  }

  /** Clears all currently-stored packet chunks (used when an ABORT packet is received) */
  public void clearStoredChunks() {
    baos.reset();
  }
}
